09 机器代码:二进制机器码如何被CPU执行
V8 首先需要将 JavaScript 编译成字节码或者二进制代码,然后再执行。二进制代码被 CPU 执行时,在编译流水线中的位置:
将源码编译成机器码
int main()
{
int x = 1
int y = 2;
int z = x + y;
return z;
}
先通过 GCC 编译器将这段 C 代码编译成二进制文件:
gcc -O0 -o code_prog code.c
接下来再将编译出来的 code_prog 程序进行反汇编,就可以看到二进制代码和对应的汇编代码:
objdump -d code_prog
左边就是编译生成的机器码,使用十六进制来展示,每一行都是一个指令,该指令可以让 CPU 执行指定的任务。
中间的部分是汇编代码,采用助记符(memonic)来编写程序,原本是二进制表示的指令,在汇编代码中可以使用单词来表示。汇编语言和机器语言是一一对应的。
一堆指令按照顺序集合在一起就组成了程序,程序的执行,本质上就是 CPU 按照顺序执行这一堆指令的过程。
CPU 是怎么执行程序的
典型的计算机系统的硬件组织结构:
程序装进内存
在程序执行之前,程序需要被装进内存。CPU 可以通过指定内存地址,从内存中读取数据,或者往内存中写入数据,有了内存地址,CPU 和内存就可以有序地交互。内存中的每个存储空间都有对应的独一无二的地址,而且地址是按照顺序排放的。
当二进制代码被加载进了内存后,内存中的每条二进制代码便都有了自己对应的地址:
取出指令、分析指令、执行指令
一旦二进制代码被装载进内存,CPU 便可以从内存中取出一条指令,然后分析该指令,最后执行该指令。把取出指令、分析指令、执行指令这三个过程称为一个 CPU 时钟周期。
CPU 是怎么知道要取出内存中的哪条指令的:
CPU 中有一个 PC 寄存器,它保存了将要执行的指令地址,当二进制代码被装载进了内存之后,系统会将二进制代码中的第一条指令的地址写入到 PC 寄存器中,到了下一个时钟周期时,CPU 便会根据 PC 寄存器中的地址,从内存中取出指令。
PC 寄存器中的指令取出来之后,系统会将下一条指令的地址更新到 PC 寄存器中。
同时 CPU 会立即分析该指令,并识别出不同的类型的指令,以及各种获取操作数的方法。
通用寄存器是 CPU 中用来存放数据的设备,不同处理器中寄存器的个数也是不一样的,之所以要通用寄存器,是因为 CPU 访问内存的速度很慢,所以 CPU 就在内部添加了一些存储设备,这些设备就是通用寄存器。通用寄存器容量小,读写速度快,内存容量大,读写速度慢。
通用寄存器通常用来存放数据或者内存中某块数据的地址,还会将某些专用的数据或者指针存储在专用的通用寄存器中 ,比如 rbp 寄存器通常是用来存放栈帧指针的,rsp 寄存器用来存放栈顶指针的,PC 寄存器用来存放下一条要执行的指令等。
几种常用的指令类型:
**加载的指令,**从内存中复制指定长度的内容到通用寄存器中,并覆盖寄存器中原来的内容。
**存储的指令,**将寄存器中的内容复制内存某个位置,并覆盖掉内存中的这个位置上原来的内容。
**更新指令,**复制两个寄存器(或一块寄存器和一块内存)中的内容到 ALU 中,ALU 将两个字相加,并将结果存放在其中的一个寄存器中,并覆盖该寄存器中的内容。
**跳转指令,**从指令本身抽取出一个字,这个字是下一条要执行的指令的地址,并将该字复制到 PC 寄存器中,并覆盖掉 PC 寄存器中原来的值。
IO 读 / 写指令,从一个 IO 设备中复制指定长度的数据到寄存器中,也可以将一个寄存器中的数据复制到指定的 IO 设备。